﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace Cosbi.TeamFoundationServer.BranchCheckInPolicy
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    using Microsoft.TeamFoundation.VersionControl.Client;

    [Serializable]
    public class BranchCheckInPolicy : PolicyBase
    {

        [NonSerialized]
        IPendingCheckin pendingCheckin;

        #region Failure messages

        private static readonly Func<int, string> NoParentFailure =
            workItemId => String.Format("BCIP01: Expected a 'User Story' of type 'Feature' as parent of dev task '{0}'.", workItemId);

        private static readonly Func<int, string> TooManyAncestorsFailure =
            workItemId => String.Format("BCIP02: Only one 'User Story' of type 'Feature' can be ancestor of dev task '{0}'.", workItemId);

        private static readonly Func<string, string, string> WrongBranchFailure =
            (branch, serverItem) => String.Format("BCIP03: Item '{1}' does not belong to the expected branch '{0}'.", branch, serverItem);

        private static readonly Func<int, string> NoBranchFailure =
            workItemId => String.Format("BCIP04: The work item '{0}' does not have an associated branch.", workItemId);

        private static readonly Func<string> NotConsinstentBranchesInWorkItemsFailure =
            () => "BCIP05: Oops! All the work items must be associated to the same branch.";

        #endregion

        public override void Activate(PolicyFailure failure)
        {
            base.Activate(failure);
            switch (failure.Message.Substring(4, 2))
            {
                case "01":
                case "02":
                    MessageBox.Show("All development tasks must exactly have one 'User Story' of type 'Feature' in their ancestors tree.", "Branch check in policy", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    break;
                case "03":
                    MessageBox.Show("The branch set in one of the associated bugs or in the 'User Story' of type 'Feature' in the ancestor tree of one of the checked work items is not the same as one of the checked versioned items.", "Branch check in policy", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    break;
                case "04":
                    MessageBox.Show("The branch is not set in some of the associated bugs or in the 'User Story' of type 'Feature' in the ancestor tree of some of the checked work items.", "Branch check in policy.", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    break;
                case "05":
                    MessageBox.Show("The branches of the associated bugs or 'User Story' of type 'Feature' in the ancestor tree of the checked work items are not consistent.", "Branch check in policy", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    break;
                default:
                    // NOOP
                    break;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this check-in policy has an editable configuration
        /// </summary>
        /// <value><c>true</c> if this instance can edit; otherwise, <c>false</c>.</value>
        public override bool CanEdit
        {
            get
            {
                return true;
            }
        }

        public override bool Edit(IPolicyEditArgs policyEditArgs)
        {
            var configurationWindow = new ConfigurationWindow();
            configurationWindow.ShowDialog(policyEditArgs.Parent);
            return true;
        }

        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <value>The type.</value>
        public override string Type
        {
            get { return "Branch check in policies"; }
        }

        /// <summary>
        /// Gets the type description.
        /// </summary>
        /// <value>The type description.</value>
        public override string TypeDescription
        {
            get { return "Ensures the user checks in inside the branch specified in the check in associated work items."; }
        }

        /// <summary>
        /// Gets the description.
        /// </summary>
        /// <value>The description.</value>
        public override string Description
        {
            get { return this.Type; }
        }

        public string InstallationUrl
        {
            get { return "http://visualstudiogallery.msdn.microsoft.com/en-us/1e2c17c1-7403-4aa3-9b5a-4e73da9319c3"; }
        }

        public override string InstallationInstructions
        {
            get
            {
                return string.Format("{0} is not installed. Go to Tools > Extenstion manager and search online for it.",
                                     this.Type);
            }
        }

        #region Settings

        private const string BranchFieldName = "Cosbi.Branch";
        private const string BugWorkItemTypeName = "Bug";
        private const string TaskWorkItemTypeName = "Task";
        private const string FeatureWorkItemTypeName = "Feature";
        private const string UserStoryTypeFieldName = "Cosbi.UserStoryType";

        #endregion


        #region Methods

        /// <summary>
        /// Dispose method unsubscribes to the event so we don't get into 
        /// scenarios that can create null ref exceptions
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();
            pendingCheckin.PendingChanges.CheckedPendingChangesChanged -= ThingsChanged;
        }

        private static IEnumerable<WorkItem> GetAncestors(WorkItemStore workItemStore, WorkItem wi)
        {
            // Search for a parent in the work item links
            var parentList = wi.Links.
                OfType<RelatedLink>().
                Where(link => link.LinkTypeEnd.Name == "Parent").
                Select(link => GetWorkItem(workItemStore, link.RelatedWorkItemId));
            if (parentList.Any()) parentList = parentList.Concat(GetAncestors(workItemStore, parentList.Single()));
            return parentList;
        }

        private static WorkItem GetWorkItem(WorkItemStore workItemStore, long id)
        {

            var result =
                workItemStore.Query(String.Format(@"SELECT [System.Id] FROM WorkItems WHERE [System.Id] = {0}",
                                    id));

            Debug.Assert(result.Count <= 1);

            return result.Count == 1 ? result[0] : null;

        }

        public void AddFailure(List<PolicyFailure> failures, string description)
        {
            failures.Add(new PolicyFailure(description, this));
        }

        /// <summary>
        /// Evaluates the pending changes for policy violations
        /// </summary>
        /// <returns></returns>
        public override PolicyFailure[] Evaluate()
        {

            // The policy failures that will be reported to Visual Studio
            var failures = new List<PolicyFailure>();

            // If there are pending changes
            if (pendingCheckin.PendingChanges.CheckedPendingChanges.Length == 0)
                return failures.ToArray();

            // Get services
            var workItemStore =
                (WorkItemStore)
                pendingCheckin.PendingChanges.CheckedPendingChanges.First().VersionControlServer.TeamProjectCollection.
                    GetService(typeof(WorkItemStore));

            // Find interesting checked workitems
            var bugsAndTasksInfo = pendingCheckin.WorkItems.CheckedWorkItems.
                Where(x => x.WorkItem.Type.Name == BugWorkItemTypeName ||
                           x.WorkItem.Type.Name == TaskWorkItemTypeName).ToList();

            // Search for user stories
            string branch = null;
            var branchIsCorrect = false;
            foreach (var workItem in bugsAndTasksInfo.Select(wiInfo => wiInfo.WorkItem))
            {

                var workItemsWithBranchInfo =
                    workItem.Type.Name == BugWorkItemTypeName
                        ? new List<WorkItem> { workItem }
                        : GetAncestors(workItemStore, workItem).
                              Where(wi => (string)wi.Fields[UserStoryTypeFieldName].Value == FeatureWorkItemTypeName).
                              ToList();

                if (workItemsWithBranchInfo.Count == 0)
                    AddFailure(failures, NoParentFailure(workItem.Id));

                else if (workItemsWithBranchInfo.Count > 1)
                    AddFailure(failures, TooManyAncestorsFailure(workItem.Id));

                else // just one ancestor or a  "b u g"
                {

                    var ancestor = workItemsWithBranchInfo[0];

                    var ancestorBranch = ancestor.Fields[BranchFieldName].Value as string;
                    if (ancestorBranch != null) ancestorBranch = ancestorBranch.Trim();

                    branchIsCorrect = SetBranch(ref branch, ancestorBranch, failures, workItem.Id);

                    if (!branchIsCorrect) break;

                }

            }

            if (branchIsCorrect)
                CheckBranchesConsistency(pendingCheckin.PendingChanges.CheckedPendingChanges, branch, failures);

            // Returns
            return failures.ToArray();
        }

        private void CheckBranchesConsistency(IEnumerable<PendingChange> pendingChanges, string branch, List<PolicyFailure> failures)
        {

            var wrongBranchInfo = pendingChanges.
                Where(change => !change.ServerItem.StartsWith(branch)).
                Select(change => change.ServerItem);

            failures.AddRange(wrongBranchInfo.Select(serverItem => new PolicyFailure(WrongBranchFailure(branch, serverItem), this)));

        }

        private bool SetBranch(ref string branch, string ancestorBranch, List<PolicyFailure> failures, int workItemId)
        {

            if (branch == null)
            {

                if (String.IsNullOrEmpty(ancestorBranch))
                {
                    AddFailure(failures, NoBranchFailure(workItemId));
                    return false;
                }
                branch = ancestorBranch;

            }
            else if (branch != ancestorBranch)
            {
                AddFailure(failures, NotConsinstentBranchesInWorkItemsFailure());
                return false;
            }

            return true;

        }

        /// <summary>
        /// Initializes this policy for the specified pending checkin.
        /// </summary>
        /// <param name="pendingcheckin">The pending checkin.</param>
        public override void Initialize(IPendingCheckin pendingcheckin)
        {

            base.Initialize(pendingcheckin);

            this.pendingCheckin = pendingcheckin;

            pendingcheckin.PendingChanges.CheckedPendingChangesChanged += ThingsChanged;
            pendingcheckin.WorkItems.CheckedWorkItemsChanged += ThingsChanged;

        }

        /// <summary>
        /// Subscribes to the ThingsChanged event 
        /// and reevaluates the policy. You should not do this if your policy takes 
        /// a long time to evaluate since this will get fired every time there is a
        /// change to one of the items in the pending changes window.
        /// </summary>
        /// <param name="sender" >The source of the event.</param>
        /// <param name="e" >The <see cref="System.EventArgs" /> instance containing the event data.</param>
        void ThingsChanged(object sender, EventArgs e)
        {
            if (!Disposed)
            {
                OnPolicyStateChanged(Evaluate());
            }
        }

        #endregion Methods
    }
}
